Skip to content

feat(persona): guided persona builder over SOUL.md (#4253, PR1)#4412

Open
M3gA-Mind wants to merge 3 commits into
tinyhumansai:mainfrom
M3gA-Mind:feat/GH-4253-persona-builder
Open

feat(persona): guided persona builder over SOUL.md (#4253, PR1)#4412
M3gA-Mind wants to merge 3 commits into
tinyhumansai:mainfrom
M3gA-Mind:feat/GH-4253-persona-builder

Conversation

@M3gA-Mind

@M3gA-Mind M3gA-Mind commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Adds a guided persona builder so non-technical users can create/edit their assistant's persona through labeled fields instead of hand-writing SOUL.md.
  • Three friendly fields — Personality, Communication style, About you — map to named ## sections and are spliced in place, so SOUL.md stays the single source of truth the runtime injects. Every other byte (title, intro, hand-written sections) is preserved.
  • Guided is the default; the existing raw markdown editor stays behind an Advanced toggle (no capability regression).
  • Reuses the existing openhuman.workspace_file_read/write/reset RPC — no Rust/core changes.
  • First slice of the phased Guided persona builder and memory dashboard for non-technical users #4253 feature (persona builder + memory dashboard). Plan approved separately; later PRs cover role templates, the memory dashboard, state labels, project isolation, and conflict handling.

Problem

Feedback on #4253: the SOUL.md concept is strong, but users don't know how to write a good persona. The editor today is a raw markdown textarea. Non-technical users need a structured way to shape identity/behavior without understanding the file format — while keeping the assistant runtime's source of truth intact (not disconnected UI-only state).

Solution

  • personaSections.ts: a lossless, idempotent parser/serializer. parsePersonaFields reads the managed sections; applyPersonaField replaces only the target section's body (preserving surrounding whitespace), appends a ## About You block on demand, and returns the input unchanged when nothing changed. Deeper ### headings stay part of a section; matching is exact and case-insensitive.
  • PersonaGuidedFields.tsx: the structured form. Persona = identity/behavior prose only; it links out to Settings → Agent access for permissions/tools rather than duplicating that config.
  • PersonaPanel.tsx: Guided/Advanced mode toggle sharing one Save/Reset path; the raw text remains the single working value both modes edit.
  • i18n: 13 new settings.persona.builder.* keys added to en and all 13 locale files (parity 13/13 each).

Submission Checklist

  • Tests added or updated — personaSections.test.ts (parser: parse, idempotency, splice-preserves-other-bytes, append, clear, heading edge cases, round-trip) and extended PersonaPanel.test.tsx (guided default, guided-splice-save over RPC, Advanced raw-edit save, Agent-access link, reset/error paths).
  • Diff coverage ≥ 80% — new logic (personaSections.ts) and UI (PersonaGuidedFields/PersonaPanel) are exercised by the tests above. Please confirm in CI.
  • Coverage matrix updated — N/A: no new feature ID; enhances the existing Persona panel.
  • All affected feature IDs listed under Related — N/A: no matrix feature-behaviour change.
  • No new external network dependencies introduced.
  • Manual smoke checklist updated if this touches release-cut surfaces — N/A: additive Settings UI, no release-cut surface.
  • Linked issue closed via Closes #NNN — this is PR1 of several; intentionally references (not closes) Guided persona builder and memory dashboard for non-technical users #4253.

Impact

  • Desktop UI only (Settings → Personality). No core, schema, or migration changes. Persona persistence path is unchanged (same RPC + file); this only changes how the content is authored.

Related


AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

Validation Run

  • pnpm --filter openhuman-app format:check — Prettier --check passes; also green in CI (Frontend Checks lane).
  • pnpm typechecktsc --noEmit passes (exit 0); also green in CI (Frontend Checks).
  • Focused tests — personaSections.test.ts + PersonaPanel.test.tsx run green (22 passed); pnpm i18n:check also passes (0 missing/extra across all locales). Confirmed by the passing Frontend Checks CI lane.
  • Rust fmt/check (if changed): N/A — no Rust changed
  • Tauri fmt/check (if changed): N/A — no Tauri changed

Validation Blocked

  • command: N/A
  • error: N/A
  • impact: N/A

Behavior Changes

  • Intended behavior change: Settings → Personality defaults to a structured persona editor; raw markdown moves under an Advanced toggle.
  • User-visible effect: Non-technical users can author a persona via fields; power users keep full raw editing.

Parity Contract

  • Legacy behavior preserved: Raw SOUL.md editing, Save, Reset, and the display-name/description store are all intact.
  • Guard/fallback/dispatch parity checks: Same workspace_file_* RPC and allowlist; guided edits produce plain SOUL.md markdown.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): None
  • Canonical PR: This PR
  • Resolution: N/A

Summary by CodeRabbit

  • New Features

    • Added a Persona editor with two SOUL.md editing modes: guided and advanced.
    • Guided mode provides structured fields for personality, voice/style, and about, while advanced mode shows the raw editor.
    • Added localized UI text, prompts, preserved-content notes, and security-related help across multiple languages.
  • Bug Fixes

    • Improved the reliability of SOUL editor load/save/reset and navigation behavior in the Persona settings panel.
  • Tests

    • Updated Persona panel and markdown section tests to consistently wait for the editor to finish loading.

Add a structured, non-technical persona editor that maps friendly fields
(Personality, Communication style, About you) to named SOUL.md sections and
splices them in place, keeping SOUL.md the runtime source of truth. Guided is
the default; the raw markdown editor stays behind an Advanced toggle. Reuses
the existing workspace_file_read/write/reset RPC — no core changes.

Part of the phased tinyhumansai#4253 work (PR1 of N).
@M3gA-Mind M3gA-Mind requested a review from a team July 2, 2026 11:44
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0ae9a90e-c926-427b-b2a3-67f33b524838

📥 Commits

Reviewing files that changed from the base of the PR and between 7c750b4 and 1a3430d.

📒 Files selected for processing (2)
  • app/src/components/settings/panels/persona/personaSections.test.ts
  • app/src/components/settings/panels/persona/personaSections.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/src/components/settings/panels/persona/personaSections.test.ts
  • app/src/components/settings/panels/persona/personaSections.ts

📝 Walkthrough

Walkthrough

Adds a guided/advanced editing mode for SOUL.md persona files. Introduces a managed-section parser/writer utility, a new PersonaGuidedFields component, PersonaPanel mode switching, updated tests, and new settings.persona.builder.* translation keys across locales.

Changes

Guided persona editor

Layer / File(s) Summary
Persona section parsing and editing utility
app/src/components/settings/panels/persona/personaSections.ts, app/src/components/settings/panels/persona/personaSections.test.ts
New module parses and losslessly splices managed ## <heading> sections (personality, voice, about) within SOUL.md, with parsePersonaFields, applyPersonaField, applyPersonaFields, and matching tests.
PersonaGuidedFields component
app/src/components/settings/panels/persona/PersonaGuidedFields.tsx
New component renders personality/voice/about textareas backed by the section utility, splicing edits back into raw SOUL.md text, with i18n labels and a security note linking to agent access.
PersonaPanel mode toggle wiring
app/src/components/settings/panels/PersonaPanel.tsx
Adds soulMode state and a guided/advanced toggle that conditionally renders PersonaGuidedFields or the existing raw textarea editor, reusing existing save/reset logic.
PersonaPanel test updates
app/src/components/settings/panels/PersonaPanel.test.tsx
Adds awaitLoaded/openAdvanced helpers and updates existing tests to gate on loaded state and mode switching, plus new default-mode and mode-switch coverage.
Persona builder translation keys
app/src/lib/i18n/ar.ts, bn.ts, de.ts, en.ts, es.ts, fr.ts, hi.ts, id.ts, it.ts, ko.ts, pl.ts, pt.ts, ru.ts, zh-CN.ts
Adds settings.persona.builder.* keys for mode labels, field placeholders, preserved-section note, and security link in all locales; removes obsolete SOUL error keys in zh-CN.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant PersonaPanel
  participant PersonaGuidedFields
  participant personaSections

  User->>PersonaPanel: toggle to guided mode
  PersonaPanel->>PersonaGuidedFields: render(soulDraft)
  PersonaGuidedFields->>personaSections: parsePersonaFields(soulDraft)
  personaSections-->>PersonaGuidedFields: PersonaFields
  User->>PersonaGuidedFields: edit personality/voice/about field
  PersonaGuidedFields->>personaSections: applyPersonaField(soul, key, value)
  personaSections-->>PersonaGuidedFields: updated soul text
  PersonaGuidedFields-->>PersonaPanel: onChange(updatedSoul)
  User->>PersonaPanel: click Save
  PersonaPanel->>PersonaPanel: writePersonaFile RPC
Loading

Poem

A rabbit hops from plain to guide,
With tidy fields and markdown spliced. 🐇
Voice and personality now bloom,
While advanced mode still has its room.
New words hop through many lands,
In SOUL.md, the editor stands.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: a guided persona builder for SOUL.md.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 941d38a2fd

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

*/
export function applyPersonaField(soul: string, key: PersonaFieldKey, value: string): string {
const heading = HEADING_FOR[key];
const nextBody = value.trim();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve live textarea newlines

In guided mode this helper runs on every textarea onChange, so trimming the draft before splicing immediately discards any trailing newline the user just typed. When a user presses Enter at the end of the Personality or Communication style field to add another paragraph/bullet, readSection(...) === nextBody can return the original SOUL.md and React re-renders without the newline, causing the next characters to be appended to the previous line. Since the default managed sections are multiline lists, preserve the live value here and only normalize for comparison/save if needed.

Useful? React with 👍 / 👎.

Prettier-only formatting of the persona builder components/tests and the
guided-persona i18n key additions across all locale files.
@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

Fixed Frontend Checks. The lane failed at format:check — Prettier flagged 16 files (the persona-builder components/tests and the new guided-persona i18n key additions across all locales). Ran prettier --write on all 16.

Verified locally, all green: prettier --check ., tsc --noEmit, eslint (0 errors), pnpm i18n:check (0 missing / 0 extra across all locales — parity holds), and the persona specs (personaSections.test.ts, PersonaPanel.test.tsx) pass 22/22. Formatting only — no behavior change.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
app/src/components/settings/panels/persona/personaSections.ts (1)

35-39: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

HEADING_FOR duplicates data already in PERSONA_SECTIONS.

Both structures map the same keys to the same heading strings. Consider deriving HEADING_FOR from PERSONA_SECTIONS to avoid the two falling out of sync if a heading is ever renamed.

♻️ Proposed refactor
-const HEADING_FOR: Record<PersonaFieldKey, string> = {
-  personality: 'Personality',
-  voice: 'Voice',
-  about: 'About You',
-};
+const HEADING_FOR: Record<PersonaFieldKey, string> = Object.fromEntries(
+  PERSONA_SECTIONS.map(({ key, heading }) => [key, heading])
+) as Record<PersonaFieldKey, string>;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/settings/panels/persona/personaSections.ts` around lines
35 - 39, HEADING_FOR currently repeats the same heading strings already defined
in PERSONA_SECTIONS, so keep the mappings in sync by deriving HEADING_FOR from
PERSONA_SECTIONS instead of hardcoding a second source of truth. Update
personaSections.ts so the heading lookup is built from PERSONA_SECTIONS using
the existing PersonaFieldKey entries, and preserve the current values for
personality, voice, and about while reusing the same symbols.
app/src/components/settings/panels/persona/personaSections.test.ts (1)

67-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding a regression test for the clear→refill whitespace-growth case.

The current "empties the body but keeps the heading" test doesn't cover refilling after clearing, which is where the lead/trail overlap bug in applyPersonaField (see personaSections.ts) manifests. A test like applyPersonaField(applyPersonaField(SOUL, 'voice', ''), 'voice', 'Be terse.') should assert the surrounding whitespace matches a direct apply.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/settings/panels/persona/personaSections.test.ts` around
lines 67 - 72, Add a regression test in personaSections.test.ts for the
clear-then-refill case around applyPersonaField: after clearing the 'voice'
field from SOUL, apply a new non-empty value like 'Be terse.' and assert the
resulting text matches the same output as a direct apply to SOUL. Use
parsePersonaFields and applyPersonaField in the existing personaSections test
suite to verify the surrounding whitespace/section spacing stays identical and
does not grow after the clear→refill path.
app/src/components/settings/panels/PersonaPanel.tsx (1)

208-225: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider a small data-driven render for the two mode buttons.

The guided/advanced buttons are nearly identical (only variant/aria-pressed/onClick/label differ). Could be collapsed into a .map() over a small config array for less duplication, but this is purely cosmetic given there are only two buttons.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/settings/panels/PersonaPanel.tsx` around lines 208 - 225,
The guided and advanced mode buttons in PersonaPanel are duplicated aside from a
few props, so replace the repeated Button blocks with a small data-driven render
using a config array and .map(). Keep the existing behavior intact by preserving
the current soulMode checks, setSoulMode calls, test IDs, and translated labels
while moving the shared Button markup into one reusable path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/components/settings/panels/persona/personaSections.ts`:
- Around line 102-106: The live update path in applyPersonaField is trimming the
incoming textarea value on every keystroke, which breaks typing of trailing
spaces/newlines in PersonaGuidedFields. Update applyPersonaField so
readSection/HEADING_FOR is still used to compare normalized content for the
no-op check, but preserve the raw value when writing the updated section back
into the document; defer trimming to save/serialize time instead of applying it
to every onChange update.
- Around line 108-120: The whitespace-preservation logic in personaSections.ts
is double-counting newlines when a section body is only whitespace, because the
lead and trail matches overlap on the same raw string. Update the section
replacement path in the function that uses findSectionSpan and splices nextBody
so that leading/trailing newline capture is computed non-overlapping or
normalized when raw contains only newlines, ensuring repeated clear/refill
cycles stay idempotent and do not grow whitespace.

---

Nitpick comments:
In `@app/src/components/settings/panels/persona/personaSections.test.ts`:
- Around line 67-72: Add a regression test in personaSections.test.ts for the
clear-then-refill case around applyPersonaField: after clearing the 'voice'
field from SOUL, apply a new non-empty value like 'Be terse.' and assert the
resulting text matches the same output as a direct apply to SOUL. Use
parsePersonaFields and applyPersonaField in the existing personaSections test
suite to verify the surrounding whitespace/section spacing stays identical and
does not grow after the clear→refill path.

In `@app/src/components/settings/panels/persona/personaSections.ts`:
- Around line 35-39: HEADING_FOR currently repeats the same heading strings
already defined in PERSONA_SECTIONS, so keep the mappings in sync by deriving
HEADING_FOR from PERSONA_SECTIONS instead of hardcoding a second source of
truth. Update personaSections.ts so the heading lookup is built from
PERSONA_SECTIONS using the existing PersonaFieldKey entries, and preserve the
current values for personality, voice, and about while reusing the same symbols.

In `@app/src/components/settings/panels/PersonaPanel.tsx`:
- Around line 208-225: The guided and advanced mode buttons in PersonaPanel are
duplicated aside from a few props, so replace the repeated Button blocks with a
small data-driven render using a config array and .map(). Keep the existing
behavior intact by preserving the current soulMode checks, setSoulMode calls,
test IDs, and translated labels while moving the shared Button markup into one
reusable path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f3e09482-8e59-4ab1-b56e-d4890f5793d1

📥 Commits

Reviewing files that changed from the base of the PR and between f979bfa and 7c750b4.

📒 Files selected for processing (19)
  • app/src/components/settings/panels/PersonaPanel.test.tsx
  • app/src/components/settings/panels/PersonaPanel.tsx
  • app/src/components/settings/panels/persona/PersonaGuidedFields.tsx
  • app/src/components/settings/panels/persona/personaSections.test.ts
  • app/src/components/settings/panels/persona/personaSections.ts
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/id.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/zh-CN.ts

Comment thread app/src/components/settings/panels/persona/personaSections.ts Outdated
Comment thread app/src/components/settings/panels/persona/personaSections.ts
@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

Also ticked the PR Submission Checklist honestly (it was the last soft-gate red). The 3 open items were all verified: prettier --check ✓, tsc --noEmit ✓ (exit 0), and the persona specs pass 22/22 — all also confirmed by the now-green Frontend Checks CI lane. No fabricated items.

…yPersonaField

CodeRabbit + Codex review on tinyhumansai#4253:
- Trimming the value on every keystroke stripped a trailing space/newline the
  user just typed (guided textareas are multiline lists). Trim only for the
  no-op comparison; write the raw value back into the document and defer trimming
  to save/serialize.
- Lead/trail newline capture overlapped when the section body was pure whitespace
  (right after a clear): `^\n*` and `\n*$` both matched the whole string, so a
  clear→refill cycle doubled the surrounding blank lines and silently grew the
  document, breaking the file's lossless/idempotent guarantee. Split a
  pure-newline body canonically (one leading blank, remainder trailing) so the
  cycle converges to the same document a direct apply produces.

Regression test: repeated clear→refill of a section is idempotent (twice === once)
and preserves the byte-exact round-trip. 45 persona tests pass; typecheck clean.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant